﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Reflection;
using MetaCollection.Framework.Configuration;
using MetaCollection.Framework.Model;
using MetaCollection.Framework.Provider;
using MetaCollection.Framework.Repository;
using MetaCollection.Framework.Index;
using Lucene.Net.Search;

namespace MetaCollection.Framework
{
	/// <summary>
	/// The <see cref="RequestHandlerFactory"/> class processes the collectionConfiguration to allow Request
	/// Handlers to be made cheaply. All work with the MetaCollection item repositories is done with the
	/// a RequestHandler.
	/// 
	/// All items within the collection are accessed by their heirarchical path. (A singlely linked list)
	/// 
	/// \\RootItem\SubItem1\SubItem2\Item54  [Item 54 is identified within it's provider's collection with a GUID]
	/// 
	/// The Items in the path are each associated with an ItemSpecification and processed by IProviders.  These providers
	/// instanced and bound to a RequestHandler to process requests using given item and resource repositories.
	/// 
	/// For the <see cref="MetaCollection.Framework.Provider.ResourceProvider"/> the configuration will
	/// specify the embedded IItemRepository and IResourceRepository that control persistence of Items
	/// and Resources.  The ResourceProvider class implements IRequestSink events to provide the implementation
	/// of the request logic.
	/// 
	/// The following diagram shows the order of instance nesting used for handling requests. In
	/// this example there are two different instances of RequestHandlers as defined by the unique
	/// ResourceProviders.  There is also a global provider which may or may not directly influence
	/// the resolution of item requests.  A typical use of a global provider is for logging or caching.
	/// 
	/// RequestHandlerFactory
	///  - RequestHandler
	///    - IProvider (ResourceProvider #1)
	///    - IProvider (ResourceProvider #2)
	///    - IProvider (Global Event Provider)
	///  - IItemRepository
	///  - IResourceRepository
	///  - IModelRepository
	/// </summary>
	/// <remarks>Why is this a static class?  Well, this class and all of the instance data it creates
	/// are defined by configuration.  As long as the configuration is the same these instances are 
	/// valid and unchanging.  When the configuration changes in a web environment the process restarts
	/// and this class will be reinstantiated with the new configuration.</remarks>
	public static class RequestHandlerFactory
	{
		/// <summary>
		/// A collection of all the defined root items in this instance.  Each of these items should
		/// be an entry point into a collection of [1..many] items
		/// </summary>
		static Dictionary<string, IItemInstance> RootItems = new Dictionary<string, IItemInstance>();
		/// <summary>
		/// A the distributed item repository contains references and logic mapping for all registered item repositories
		/// </summary>
		static DistributedItemRepository ItemRepo = new DistributedItemRepository();
#if !DEBUG
		static CachingItemRepository CachingItem;
#endif
		/// <summary>
		/// A the distributed resource repository contains references and logic mapping for all registered resource repositories
		/// </summary>
		static DistributedResourceRepository ResourceRepo = new DistributedResourceRepository();
		/// <summary>
		/// A the distributed model repository contains references and logic mapping for all registered model repositories
		/// </summary>
		static DistributedModelRepository ModelRepo = new DistributedModelRepository();

		/// <summary>
		/// An item audit logging repository.
		/// </summary>
		static IAuditRepository AuditRepo = null;
		/// <summary>
		/// List of all Providers known to the collection as configured.
		/// </summary>
		static List<IProvider> ProviderList = new List<IProvider>();
		/// <summary>
		/// A list of repository indexes used for querying
		/// </summary>
		static List<IRepositoryIndex> Indexes = new List<IRepositoryIndex>();
		static Exception ConfigException;

		/// <summary>
		/// Processes the Configuration Section "collectionConfiguration" and builds a set of instance backed collections.  These
		/// instances are spun off into RequestHandlers by calling the Create method.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Justification="We are not raising the exception here, we are recording it so it can be raised later."),
		System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Processing the Configuration is a multistep process best done in a constructor and not inline")]
		static RequestHandlerFactory()
		{
			CollectionConfigurationSection config = ConfigurationManager.GetSection("collectionConfiguration") as CollectionConfigurationSection;
			if (config == null)
				ConfigException = new ConfigurationErrorsException("Collection Configuration Section is missing.");
			else if (config.RootNodes.Count < 1)
				ConfigException = new ConfigurationErrorsException("At least one root node is required in the collection configuration.");
			else
			{
				LoadRootItems(config);
				LoadRepositoryInstances(config.Repositories);
				LoadProviderList(config.Providers.Cast<ProviderReferenceElement>());
#if !DEBUG
				CachingItem = new CachingItemRepository(ItemRepo);
#endif
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ItemApplication"/> class.
		/// </summary>
		public static IRequestHandler CreateRequestHandler()
		{
			if (ConfigException != null)
				throw ConfigException;
#if DEBUG
			IRequestHandler handler = new RequestHandler(RootItems, ItemRepo, ResourceRepo, ModelRepo, AuditRepo);
#else
			IRequestHandler handler = new RequestHandler(RootItems, CachingItem, ResourceRepo, ModelRepo, AuditRepo);
#endif
			try
			{
				foreach (IProvider provider in ProviderList)
				{
					handler.Register(provider, provider.SupportedEvents);
				}
			}
			catch
			{
				if (handler != null)
					handler.Dispose();
				throw;
			}
			return handler;
		}

		/// <summary>
		/// Creates an index searcher that can execute queries against the configured Repository Indexes.
		/// </summary>
		/// <returns>a Searcher or null if there are no configured indexes.</returns>
		[CLSCompliant(false)]
		public static Lucene.Net.Search.Searcher CreateIndexSearcher()
		{
			if (Indexes.Count < 1)
				return null;

			var searchables = new List<Lucene.Net.Search.Searchable>();
			foreach (var i in Indexes)
			{
				try
				{
					searchables.Add(new IndexSearcher(i.CreateReader()));
				}
				catch (Lucene.Net.Index.CorruptIndexException)
				{
					//Not sure what we want to do here, if the indexsearcher is having issues.  Possibly re-create the index.
					//We certainly don't want to stop searching all the indexes if one is having an issue; so for now, just move on.
				}
			}
			return new Lucene.Net.Search.ParallelMultiSearcher(searchables.ToArray());
		}

		/// <summary>
		/// Gets a list of all currently registered item specifications.
		/// </summary>
		/// <value>The registered item specifications.</value>
		public static IEnumerable<IItemSpecification> RegisteredItemSpecifications
		{
			get
			{
				return ModelRepo.RegisteredSpecifications;
			}
		}
		/// <summary>
		/// Loads the repository instances.
		/// </summary>
		/// <param name="repositories">The repositories.</param>
		private static void LoadRepositoryInstances(TypeReferenceCollection repositories)
		{
			foreach (TypeReferenceElement repository in repositories)
			{
				LoadRepositoryInstance(repository);
			}
		}

		/// <summary>
		/// Loads the repository instance.
		/// </summary>
		/// <param name="repository">The repository.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AuditRepository", Justification="This is spelled correctly.")]
		private static object LoadRepositoryInstance(TypeReferenceElement repository)
		{
			if (String.IsNullOrEmpty(repository.TypeName))
				throw new ArgumentException("Referenced type must be given to load a repository reference.  Check your repository typeName references in the config file.");

			object repositoryInstance = GetInstance(repository.AssemblyName, repository.TypeName);
			switch (repository.InterfaceType)
			{
				case RepositoryReferenceType.Item:
					IItemRepository itemRepo = repositoryInstance as IItemRepository;
					if (itemRepo == null)
						throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "{0} does not implement the IItemRepository interface and could not be loaded.", repository.TypeName));
					itemRepo.Initialize(repository.ConfigData);
					ItemRepo.RegisterRepository(repository.Name, itemRepo);
					break;
				case RepositoryReferenceType.Resource:
					IResourceRepository resourceRepo = repositoryInstance as IResourceRepository;
					if (resourceRepo == null)
						throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "{0} does not implement the IResourceRepository interface and could not be loaded.", repository.TypeName));
					resourceRepo.Initialize(repository.ConfigData);
					ResourceRepo.RegisterRepository(repository.Name, resourceRepo);
					break;
				case RepositoryReferenceType.Model:
					IModelRepository modelRepo = repositoryInstance as IModelRepository;
					if (modelRepo == null)
						throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "{0} does not implement the IModelRepository interface and could not be loaded.", repository.TypeName));
					modelRepo.Initialize(repository.ConfigData);
					ModelRepo.RegisterRepository(repository.Name, modelRepo);
					break;
				case RepositoryReferenceType.Audit:
					IAuditRepository auditRepo = repositoryInstance as IAuditRepository;
					if (auditRepo == null)
						throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "{0} does not implement the IAuditRepository interface and could not be loaded.", repository.TypeName));
					auditRepo.Initialize(repository.ConfigData);
					if (AuditRepo != null)
						throw new ConfigurationErrorsException("Currently the AuditRepository system is not configured to support multiple audit repositories.");
					AuditRepo = auditRepo;
					break;
				case RepositoryReferenceType.Index:
					IRepositoryIndex indexRepo = repositoryInstance as IRepositoryIndex;
					if (indexRepo == null)
						throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "{0} does not implement the IRepositoryIndex interface and could not be loaded.", repository.TypeName));
					indexRepo.Initialize(repository.ConfigData);
					Indexes.Add(indexRepo);
					ProviderList.Add(indexRepo); // to allow sinking of update events
					break;
				default: throw new ConfigurationErrorsException("Repositories must be typed with Audit, Item, Index, Resource or Model.");

			}
			return repositoryInstance;
		}

		/// <summary>
		/// Adds a Provider to our ProviderList as specified in the list of providerTypes.
		/// </summary>
		/// <param name="providerTypes">The provider types.</param>
		private static void LoadProviderList(IEnumerable<ProviderReferenceElement> providerTypes)
		{
			List<ProviderReferenceElement> providers = providerTypes.ToList();

			foreach (ProviderReferenceElement providerReference in providers)
			{
				IProvider provider;
				try
				{
					provider = providerReference.CreateProvider();
				}
				catch (ConfigurationErrorsException e)
				{
					throw new ConfigurationErrorsException("This provider is invalid - " + providerReference.Text, e);
				}
				ProviderList.Add(provider);
			}
		}

		/// <summary>
		/// Loads the root items from the configuration section
		/// </summary>
		/// <param name="config">The config.</param>
		private static void LoadRootItems(CollectionConfigurationSection config)
		{
			foreach (ItemReferenceElement root in config.RootNodes)
			{
				ItemInstance rootItem = new ItemInstance();
				rootItem.Name = root.Name;
				rootItem.Id = root.Id;
				RootItems.Add(rootItem.Name, rootItem);
			}
		}

		/// <summary>
		/// Gets an instance of the type specfied by <paramref name="assemblyName"/> and <paramref name="typeName"/>.
		/// </summary>
		/// <remarks>The default constructor of the type will be invoked (no arguments passed in)</remarks>
		/// <param name="assemblyName">Name of the assembly.</param>
		/// <param name="typeName">Name of the type.</param>
		/// <returns>an instance of the type specfied</returns>
		private static Object GetInstance(string assemblyName, string typeName)
		{
			if (String.IsNullOrEmpty(typeName))
				throw new ArgumentNullException("typeName", "Type Name can not be null or empty.");

			Assembly asm;
			if (String.IsNullOrEmpty(assemblyName))
				asm = Assembly.GetCallingAssembly();
			else
				asm = Assembly.Load(assemblyName);

			Type pType = asm.GetType(typeName, true);

			return Activator.CreateInstance(pType);
		}
	}
}
